﻿using Firefly.ExtensionMethods;
using Firefly.Framework.Input;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace Firefly.Framework.Windows.Forms
{
    public class MoveableControl : Control
    {
        private Rectangle _screenDragHandle;
        private OverHandle _handles;
        private OverHandle _startHandles;
        private bool _resizing;
        private bool _dragging;
        private DockStyle _dragHandleDock;
        private Point _startResize;
        private Point _startDrag;

        [ContentSerializer(Optional = true)]
        public Rectangle DragHandle { get; set; }
        [ContentSerializer(Optional = true)]
        public DockStyle DragHandleDock
        {
            get { return _dragHandleDock; }
            set
            {
                _dragHandleDock = value;
                UpdateRectangles();
            }
        }
        [ContentSerializer(Optional = true)]
        public bool Draggable { get; set; }
        [ContentSerializer(Optional = true)]
        public int HandleSize { get; set; }
        [ContentSerializer(Optional = true)]
        public virtual Point MinimumSize { get; set; }
        [ContentSerializer(Optional = true)]
        public bool Sizeable { get; set; }

        public MoveableControl()
        {
            _resizing = false;
            _dragging = false;
            Sizeable = true;
            Draggable = true;
            DragHandle = Rectangle.Empty;
            _screenDragHandle = Rectangle.Empty;
            MinimumSize = new Point(15, 15);
            HandleSize = 5;
            _dragHandleDock = DockStyle.None;
            _startResize = Point.Zero;
            _startDrag = Point.Zero;
        }

        protected override void OnMouseDown(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            Point position = InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint();

            if (!Visible)
            {
                base.OnMouseDown(map, e);
            }
            else
            {
                BringToFront();

                if (Sizeable)
                {
                    _handles = GetOverHandle(position);

                    if (_handles.Any())
                    {
                        StartResize();
                    }
                }

                if (Draggable)
                {
                    if (_screenDragHandle.Contains(position))
                    {
                        StartDrag();
                    }
                }
            }
        }

        protected override void OnMouseUp(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            if (!Visible)
            {
                base.OnMouseUp(map, e);
            }
            else
            {
                _resizing = false;
                _dragging = false;
            }
        }

        protected void DoDrag()
        {
            Position = new Rectangle(Position.X + ((int)InputService.Instance.MouseManager.CurrentAxisPosition.X - _startDrag.X),
                Position.Y + ((int)InputService.Instance.MouseManager.CurrentAxisPosition.Y - _startDrag.Y),
                Position.Width,
                Position.Height);
            _startDrag = InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint();
            Resize();
            NotifySizeChanged();
        }

        protected virtual Point GetMinimumSize()
        {
            return MinimumSize;
        }

        protected void DoResize()
        {
            int diff;
            bool lockY = false;
            bool lockX = false;
            Point minSize = GetMinimumSize();

            if (_startHandles.Top)
            {
                diff = (int)InputService.Instance.MouseManager.CurrentAxisPosition.Y - _startResize.Y;

                if (this.Position.Height - diff >= minSize.Y)
                {
                    Position = new Rectangle(Position.X,
                        Position.Y + diff,
                        Position.Width,
                        Position.Height - diff);
                }
                else 
                { 
                    lockY = true; 
                }
            }

            if (_startHandles.Bottom)
            {
                if (this.Position.Height + ((int)InputService.Instance.MouseManager.CurrentAxisPosition.Y - _startResize.Y) >= minSize.Y)
                {
                    Position = new Rectangle(Position.X,
                        Position.Y,
                        Position.Width,
                        Position.Height + ((int)InputService.Instance.MouseManager.CurrentAxisPosition.Y - _startResize.Y));
                }
                else
                { 
                    lockY = true; 
                }
            }

            if (_startHandles.Left)
            {
                diff = (int)InputService.Instance.MouseManager.CurrentAxisPosition.X - _startResize.X;
                
                if (this.Position.Width - diff >= minSize.X)
                {
                    Position = new Rectangle(Position.X + diff,
                    Position.Y,
                    Position.Width - diff,
                    Position.Height);
                }
                else
                { 
                    lockX = true; 
                }
            }

            if (_startHandles.Right)
            {
                if (this.Position.Width + ((int)InputService.Instance.MouseManager.CurrentAxisPosition.X - _startResize.X) >= minSize.X)
                {
                    Position = new Rectangle(Position.X,
                    Position.Y,
                    Position.Width + ((int)InputService.Instance.MouseManager.CurrentAxisPosition.X - _startResize.X),
                    Position.Height);
                }
                else
                { 
                    lockX = true; 
                }
            }

            _startResize = new Point(lockX ? _startResize.X : (int)InputService.Instance.MouseManager.CurrentAxisPosition.X,
                lockY ? _startResize.Y : (int)InputService.Instance.MouseManager.CurrentAxisPosition.Y);
            Children.Sort(NaturalOrderComparer);
            Parent.RaiseBubbleEvent(this, new BubbleEventArgs(BubbleEvent.Resize, null, null));
        }

        protected void StartResize()
        {
            _resizing = true;
            _startResize = InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint();
            _startHandles = GetOverHandle(_startResize);
        }

        protected void StartDrag()
        {
            _dragging = true;
            _startDrag = InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint();
        }

        protected override void Resize()
        {
            if (LayoutSuspended)
            {
                return;
            }

            UpdateRectangles();

            switch (_dragHandleDock)
            {
                case DockStyle.Top:
                    _screenDragHandle.X = DrawRectangle.X + HandleSize;
                    _screenDragHandle.Y = DrawRectangle.Y + HandleSize;
                    _screenDragHandle.Width = DrawRectangle.Width - (HandleSize * 2);
                    _screenDragHandle.Height = DragHandle.Height;
                    break;
                case DockStyle.Bottom:
                    _screenDragHandle.X = DrawRectangle.X + HandleSize;
                    _screenDragHandle.Y = DrawRectangle.Y + DrawRectangle.Height - DragHandle.Height - HandleSize;
                    _screenDragHandle.Width = DrawRectangle.Width - (HandleSize * 2);
                    _screenDragHandle.Height = DragHandle.Height;
                    break;
                case DockStyle.Left:
                    _screenDragHandle.X = DrawRectangle.X + HandleSize;
                    _screenDragHandle.Y = DrawRectangle.Y + HandleSize;
                    _screenDragHandle.Width = DrawRectangle.Width;
                    _screenDragHandle.Height = DragHandle.Height - (HandleSize * 2);
                    break;
                case DockStyle.Right:
                    _screenDragHandle.X = DrawRectangle.X + DrawRectangle.Width - DragHandle.Width - HandleSize;
                    _screenDragHandle.Y = DrawRectangle.Y + HandleSize;
                    _screenDragHandle.Width = DragHandle.Width;
                    _screenDragHandle.Height = DrawRectangle.Height - (HandleSize * 2);
                    break;
                case DockStyle.Fill:
                    _screenDragHandle.X = DrawRectangle.X + HandleSize;
                    _screenDragHandle.Y = DrawRectangle.Y + HandleSize;
                    _screenDragHandle.Width = DrawRectangle.Width - (HandleSize * 2);
                    _screenDragHandle.Height = DrawRectangle.Height - (HandleSize * 2);
                    break;
                default:
                    _screenDragHandle.X = DrawRectangle.X + DragHandle.X;
                    _screenDragHandle.Y = DrawRectangle.Y + DragHandle.Y;
                    _screenDragHandle.Width = DragHandle.Width;
                    _screenDragHandle.Height = DragHandle.Height;
                    break;
            }

            foreach (Control c in Children) 
            { 
                c.RaiseBubbleEvent(this, new BubbleEventArgs(BubbleEvent.Resize, null, null)); 
            }
        }

        private OverHandle GetOverHandle(Point p)
        {
            OverHandle handles = new OverHandle();
            bool isOver = this.IsMouseOver(p);

            if (isOver)
            {
                handles.Left = (p.X <= this.DrawRectangle.X + HandleSize);
                handles.Top = (p.Y <= this.DrawRectangle.Y + HandleSize);
                handles.Right = (p.X >= this.DrawRectangle.X + this.DrawRectangle.Width - HandleSize);
                handles.Bottom = (p.Y >= this.DrawRectangle.Y + this.DrawRectangle.Height - HandleSize);
            }
            else
            { 
                handles.Left = false; handles.Right = false; handles.Top = false; handles.Bottom = false; 
            }

            return handles;
        }

        private void UpdateCursor(OverHandle handles)
        {
            if (Sizeable)
            {
                if ((handles.Top && handles.Left) || (handles.Bottom && handles.Right))
                { 
                    Cursor = "NWSE"; 
                }
                else if ((handles.Top && handles.Right) || (handles.Bottom && handles.Left))
                { 
                    Cursor = "NESW"; 
                }
                else if (handles.Top || handles.Bottom)
                { 
                    Cursor = "NS"; 
                }
                else if (handles.Left || handles.Right)
                { 
                    Cursor = "EW"; 
                }
                else
                { 
                    Cursor = "Default"; 
                }
            }
            else
            { 
                Cursor = "Default"; 
            }

            if (Draggable && _screenDragHandle.Contains(InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint()) && IsMouseOver(InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint()))
            {
                Cursor = "Move";
            }
        }

        #region IPipelineSerializeable Members

        public override void DeserializeContent(ContentReader input)
        {
            base.DeserializeContent(input);
            Sizeable = input.ReadBoolean();
            HandleSize = input.ReadInt32();
            Draggable = input.ReadBoolean();
            DragHandle = input.ReadObject<Rectangle>();
            _dragHandleDock = (DockStyle)input.ReadInt32();
            MinimumSize = input.ReadObject<Point>();
        }

        #endregion

        #region IUpdateable Members

        public override void Update(GameTime gameTime)
        {
            if (Enabled)
            {
                if (!InputService.Instance.MouseManager.AxisPositionInViewport)
                {
                    return;
                }

                _handles = GetOverHandle(InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint());
                UpdateCursor(_handles);

                if (Sizeable && _resizing)
                {
                    DoResize();
                }

                if (Draggable && _dragging)
                {
                    DoDrag();
                }

                base.Update(gameTime);
            }
        }

        #endregion
    }
}
